home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-orca / orca / keybindings.py < prev    next >
Encoding:
Python Source  |  2009-04-13  |  14.0 KB  |  386 lines

  1. # Orca
  2. #
  3. # Copyright 2005-2008 Sun Microsystems Inc.
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Library General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. # Library General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Library General Public
  16. # License along with this library; if not, write to the
  17. # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
  18. # Boston MA  02110-1301 USA.
  19.  
  20. """Provides support for defining keybindings and matching them to input
  21. events."""
  22.  
  23. __id__        = "$Id: keybindings.py 3971 2008-06-11 00:54:46Z joanied $"
  24. __version__   = "$Revision: 3971 $"
  25. __date__      = "$Date: 2008-06-10 20:54:46 -0400 (Tue, 10 Jun 2008) $"
  26. __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
  27. __license__   = "LGPL"
  28.  
  29. try:
  30.     # This can fail due to gtk not being available.  We want to
  31.     # be able to recover from that if possible.  The main driver
  32.     # for this is to allow "orca --text-setup" to work even if
  33.     # the desktop is not running.
  34.     #
  35.     import gtk
  36. except ImportError:
  37.     pass
  38.  
  39. import pyatspi
  40. import debug
  41. import settings
  42. import orca_state
  43.  
  44. from orca_i18n import _           # for gettext support
  45.  
  46. _keysymsCache = {}
  47. _keycodeCache = {}
  48.  
  49. def getAllKeysyms(keysym):
  50.     """Given a keysym, find all other keysyms associated with the key
  51.     that is mapped to the given keysym.  This allows us, for example,
  52.     to determine that the key bound to KP_Insert is also bound to KP_0."""
  53.  
  54.     if keysym not in _keysymsCache:
  55.         # The keysym itself is always part of the list.
  56.         #
  57.         _keysymsCache[keysym] = [keysym]
  58.  
  59.         # Find the numerical value of the keysym
  60.         #
  61.         keyval = gtk.gdk.keyval_from_name(keysym)
  62.  
  63.         if keyval != 0:
  64.             # Find the keycodes for the keysym.  Since a keysym
  65.             # can be associated with more than one key, we'll shoot
  66.             # for the keysym that's in group 0, regardless of shift
  67.             # level (each entry is of the form [keycode, group,
  68.             # level]).
  69.             #
  70.             keymap = gtk.gdk.keymap_get_default()
  71.             entries = keymap.get_entries_for_keyval(keyval)
  72.             keycode = 0
  73.             if entries:
  74.                 for entry in entries:
  75.                     if entry[1] == 0:  # group = 0
  76.                         keycode = entry[0]
  77.                         break
  78.  
  79.             # Find the keysyms bound to the keycode.  These are what
  80.             # we are looking for.
  81.             #
  82.             if keycode != 0:
  83.                 entries = keymap.get_entries_for_keycode(keycode)
  84.                 if entries:
  85.                     for entry in entries:
  86.                         keyval = entry[0]
  87.                         name = gtk.gdk.keyval_name(keyval)
  88.                         if name and (name != keysym):
  89.                             _keysymsCache[keysym].append(name)
  90.  
  91.     return _keysymsCache[keysym]
  92.  
  93. def getKeycode(keysym):
  94.     """Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that
  95.     should match the event.hw_code for key events.
  96.  
  97.     This whole situation is caused by the fact that Solaris chooses
  98.     to give us different keycodes for the same key, and the keypad
  99.     is the primary place where this happens: if NumLock is not on,
  100.     there is no telling the difference between keypad keys and the
  101.     other navigation keys (e.g., arrows, page up/down, etc.).  One,
  102.     for example, would expect to get KP_End for the '1' key on the
  103.     keypad if NumLock were not on.  Instead, we get 'End' and the
  104.     keycode for it matches the keycode for the other 'End' key.  Odd.
  105.     If NumLock is on, we at least get KP_* keys.
  106.  
  107.     So...when setting up keybindings, we say we're interested in
  108.     KeySyms, but those keysyms are carefully chosen so as to result
  109.     in a keycode that matches the actual key on the keyboard.  This
  110.     is why we use KP_1 instead of KP_End and so on in our keybindings.
  111.  
  112.     Arguments:
  113.     - keysym: a string that is a valid representation of an XKeysym.
  114.  
  115.     Returns an integer representing a key code that should match the
  116.     event.hw_code for key events.
  117.     """
  118.  
  119.     if not keysym:
  120.         return 0
  121.  
  122.     if keysym not in _keycodeCache:
  123.         keymap = gtk.gdk.keymap_get_default()
  124.  
  125.         # Find the numerical value of the keysym
  126.         #
  127.         keyval = gtk.gdk.keyval_from_name(keysym)
  128.         if keyval == 0:
  129.             return 0
  130.  
  131.         # Now find the keycodes for the keysym.   Since a keysym can
  132.         # be associated with more than one key, we'll shoot for the
  133.         # keysym that's in group 0, regardless of shift level (each
  134.         # entry is of the form [keycode, group, level]).
  135.         #
  136.         _keycodeCache[keysym] = 0
  137.         entries = keymap.get_entries_for_keyval(keyval)
  138.         if entries:
  139.             for entry in entries:
  140.                 if entry[1] == 0:  # group = 0
  141.                     _keycodeCache[keysym] = entry[0]
  142.                     break
  143.                 if _keycodeCache[keysym] == 0:
  144.                     _keycodeCache[keysym] = entries[0][0]
  145.  
  146.         #print keysym, keyval, entries, _keycodeCache[keysym]
  147.  
  148.     return _keycodeCache[keysym]
  149.  
  150. def getModifierNames(mods):
  151.     """Gets the modifier names of a numeric modifier mask as a human
  152.     consumable string.
  153.     """
  154.  
  155.     text = ""
  156.     if mods & settings.ORCA_MODIFIER_MASK:
  157.         text += _("Orca") + "+"
  158.     #if mods & (1 << pyatspi.MODIFIER_NUMLOCK):
  159.     #    text += _("Num_Lock") + "+"
  160.     if mods & 128:
  161.         # Translators: this is presented in a GUI to represent the
  162.         # "right alt" modifier.
  163.         #
  164.         text += _("Alt_R") + "+"
  165.     if mods & (1 << pyatspi.MODIFIER_META3):
  166.         # Translators: this is presented in a GUI to represent the
  167.         # "super" modifier.
  168.         #
  169.         text += _("Super") + "+"
  170.     if mods & (1 << pyatspi.MODIFIER_META2):
  171.         # Translators: this is presented in a GUI to represent the
  172.         # "meta 2" modifier.
  173.         #
  174.         text += _("Meta2") + "+"
  175.     #if mods & (1 << pyatspi.MODIFIER_META):
  176.     #    text += _("Meta") + "+"
  177.     if mods & settings.ALT_MODIFIER_MASK:
  178.         # Translators: this is presented in a GUI to represent the
  179.         # "left alt" modifier.
  180.         #
  181.         text += _("Alt_L") + "+"
  182.     if mods & settings.CTRL_MODIFIER_MASK:
  183.         # Translators: this is presented in a GUI to represent the
  184.         # "control" modifier.
  185.         #
  186.         text += _("Ctrl") + "+"
  187.     if mods & (1 << pyatspi.MODIFIER_SHIFTLOCK):
  188.         # Translators: this is presented in a GUI to represent the
  189.         # "caps lock" modifier.
  190.         #
  191.         text += _("Caps_Lock") + "+"
  192.     if mods & settings.SHIFT_MODIFIER_MASK:
  193.         # Translators: this is presented in a GUI to represent the
  194.         # "shift " modifier.
  195.         #
  196.         text += _("Shift") + "+"
  197.     return text
  198.  
  199. class KeyBinding:
  200.     """A single key binding, consisting of a keycode, a modifier mask,
  201.     and the InputEventHandler.
  202.     """
  203.  
  204.     def __init__(self, keysymstring, modifier_mask, modifiers, handler,
  205.                  click_count = 1):
  206.         """Creates a new key binding.
  207.  
  208.         Arguments:
  209.         - keysymstring: the keysymstring - this is typically a string
  210.           from /usr/include/X11/keysymdef.h with the preceding 'XK_'
  211.           removed (e.g., XK_KP_Enter becomes the string 'KP_Enter').
  212.         - modifier_mask: bit mask where a set bit tells us what modifiers
  213.           we care about (see pyatspi.MODIFIER_*)
  214.         - modifiers: the state the modifiers we care about must be in for
  215.           this key binding to match an input event (see also
  216.           pyatspi.MODIFIER_*)
  217.         - handler: the InputEventHandler for this key binding
  218.         """
  219.  
  220.         self.keysymstring = keysymstring
  221.         self.modifier_mask = modifier_mask
  222.         self.modifiers = modifiers
  223.         self.handler = handler
  224.         self.click_count = click_count
  225.         self.keycode = None
  226.  
  227.     def matches(self, keycode, modifiers):
  228.         """Returns true if this key binding matches the given keycode and
  229.         modifier state.
  230.         """
  231.  
  232.         # We lazily bind the keycode.  The primary reason for doing this
  233.         # is so that atspi does not have to be initialized before setting
  234.         # keybindings in the user's preferences file.
  235.         #
  236.         if not self.keycode:
  237.             self.keycode = getKeycode(self.keysymstring)
  238.  
  239.         if self.keycode == keycode:
  240.             result = modifiers & self.modifier_mask
  241.             return result == self.modifiers
  242.         else:
  243.             return False
  244.  
  245. class KeyBindings:
  246.     """Structure that maintains a set of KeyBinding instances.
  247.     """
  248.  
  249.     def __init__(self):
  250.         self.keyBindings = []
  251.  
  252.     def __str__(self):
  253.         result = "[\n"
  254.         for keyBinding in self.keyBindings:
  255.             result += "  [%x %x %s %d %s]\n" % \
  256.                       (keyBinding.modifier_mask,
  257.                        keyBinding.modifiers,
  258.                        keyBinding.keysymstring,
  259.                        keyBinding.click_count,
  260.                        keyBinding.handler.description)
  261.         result += "]"
  262.         return result
  263.     
  264.     def add(self, keyBinding):
  265.         """Adds the given KeyBinding instance to this set of keybindings.
  266.         """
  267.  
  268.         self.keyBindings.append(keyBinding)
  269.  
  270.     def remove(self, keyBinding):
  271.         """Removes the given KeyBinding instance from this set of keybindings.
  272.         """
  273.  
  274.         for i in range(0, len(self.keyBindings)):
  275.             if keyBinding == self.keyBindings[i]:
  276.                 del self.keyBindings[i]
  277.  
  278.     def removeByHandler(self, handler):
  279.         """Removes the given KeyBinding instance from this set of keybindings.
  280.         """
  281.         i = len(self.keyBindings)
  282.         while i > 0:
  283.             if self.keyBindings[i - 1].handler == handler:
  284.                 del self.keyBindings[i - 1]
  285.             i = i - 1
  286.  
  287.     def hasKeyBinding (self, newKeyBinding, typeOfSearch="strict"):
  288.         """Return True if keyBinding is already in self.keyBindings.
  289.  
  290.            The typeOfSearch can be:
  291.               "strict":      matches description, modifiers, key, and
  292.                              click count
  293.               "description": matches only description.
  294.               "keys":        matches the modifiers, key, and modifier mask,
  295.                              and click count
  296.               "keysNoMask":  matches the modifiers, key, and click count
  297.         """
  298.  
  299.         hasIt = False
  300.  
  301.         for keyBinding in self.keyBindings:
  302.             if typeOfSearch == "strict":
  303.                 if (keyBinding.handler.description \
  304.                     == newKeyBinding.handler.description) \
  305.                     and (keyBinding.keysymstring \
  306.                          == newKeyBinding.keysymstring) \
  307.                     and (keyBinding.modifier_mask \
  308.                          == newKeyBinding.modifier_mask) \
  309.                     and (keyBinding.modifiers \
  310.                          == newKeyBinding.modifiers) \
  311.                     and (keyBinding.click_count \
  312.                          == newKeyBinding.click_count):
  313.                     hasIt = True
  314.             elif typeOfSearch == "description":
  315.                 if keyBinding.handler.description \
  316.                     == newKeyBinding.handler.description:
  317.                     hasIt = True
  318.             elif typeOfSearch == "keys":
  319.                 if (keyBinding.keysymstring \
  320.                     == newKeyBinding.keysymstring) \
  321.                     and (keyBinding.modifier_mask \
  322.                          == newKeyBinding.modifier_mask) \
  323.                     and (keyBinding.modifiers \
  324.                          == newKeyBinding.modifiers) \
  325.                     and (keyBinding.click_count \
  326.                          == newKeyBinding.click_count):
  327.                     hasIt = True
  328.             elif typeOfSearch == "keysNoMask":
  329.                 if (keyBinding.keysymstring \
  330.                     == newKeyBinding.keysymstring) \
  331.                     and (keyBinding.modifiers \
  332.                          == newKeyBinding.modifiers) \
  333.                     and (keyBinding.click_count \
  334.                          == newKeyBinding.click_count):
  335.                     hasIt = True
  336.  
  337.         return hasIt
  338.  
  339.     def getInputHandler(self, keyboardEvent):
  340.         """Returns the input handler of the key binding that matches the
  341.         given keycode and modifiers, or None if no match exists.
  342.         """
  343.  
  344.         if not orca_state.activeScript:
  345.             return None
  346.  
  347.         candidates = []
  348.         clickCount = orca_state.activeScript.getClickCount()
  349.         for keyBinding in self.keyBindings:
  350.             if keyBinding.matches(keyboardEvent.hw_code, \
  351.                                   keyboardEvent.modifiers):
  352.                 if keyBinding.modifier_mask == keyboardEvent.modifiers and \
  353.                    keyBinding.click_count == clickCount:
  354.                     return keyBinding.handler
  355.                 candidates.append(keyBinding)
  356.  
  357.         # If we're still here, we don't have an exact match. Prefer
  358.         # the one whose click count is closest to, but does not exceed,
  359.         # the actual click count.
  360.         #
  361.         candidates.sort(cmp=lambda x, y: x.click_count - y.click_count,
  362.                         reverse=True)
  363.         for candidate in candidates:
  364.             if candidate.click_count <= clickCount:
  365.                 return candidate.handler
  366.  
  367.         return None
  368.  
  369.     def consumeKeyboardEvent(self, script, keyboardEvent):
  370.         """Attempts to consume the given keyboard event.  If these
  371.         keybindings have a handler for the given keyboardEvent, it is
  372.         assumed the event will always be consumed.
  373.         """
  374.  
  375.         consumed = False
  376.         handler = self.getInputHandler(keyboardEvent)
  377.         if handler:
  378.             consumed = True
  379.             if keyboardEvent.type == pyatspi.KEY_PRESSED_EVENT:
  380.                 try:
  381.                     handler.processInputEvent(script, keyboardEvent)
  382.                 except:
  383.                     debug.printException(debug.LEVEL_SEVERE)
  384.  
  385.         return consumed
  386.